package vh.android.multiconnector.library.broadcast;

import java.io.IOException;
import java.net.InetAddress;

import android.content.Context;
import android.content.Intent;
import android.net.DhcpInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;

public class Broadcaster {

	public static final int SENDER_DELAY = 15000; // in ms
	public static final int DEFAULT_MAX_PACKAGE_SIZE = 1024 * 4; // in bytes
	public static final String INTENT_ACTION = "vh.android.multiconnector.BROADCAST";
	protected static final String INTENT_EXTRA_APP_TAG = "__APP_TAG__";
	protected static final String INTENT_EXTRA_TO_TAG = "__TO_TAG__";
	private static final String LOG_TAG = Broadcaster.class.getName();
	private static final String INTENT_EXTRA_MAC_TAG = "__MAC_TAG__";
	private final String packageName;
	private final InetAddress inetAddress;
	private final String macAddress;
	private final int port;
	private BroadcastReceiver receiver;
	private BroadcastSender sender;
	private int mDefaultMaxPackageSize = DEFAULT_MAX_PACKAGE_SIZE;
	private String applicationTag;
	private final WifiManager wifiMgr;
	private WifiManager.MulticastLock multicastLock;
	private String mWifiIP;

	/**
	 * @param pContext
	 *            application content
	 * @param port
	 *            port to listen for broadcasts
	 * @param applicationTag
	 * @throws IOException
	 */
	public Broadcaster(Context pContext, int port, String applicationTag)
			throws IOException {
		wifiMgr = (WifiManager) pContext.getSystemService(Context.WIFI_SERVICE);
		macAddress = wifiMgr.getConnectionInfo().getMacAddress();

		inetAddress = buildBroadcastAddress(pContext);
		mWifiIP = getIpAddr(pContext);

		packageName = pContext.getPackageName();

		this.port = port;
		this.applicationTag = applicationTag;

		receiver = new BroadcastReceiver(inetAddress, this.port,
				getMaxPackageSize(), macAddress, this.applicationTag);
		sender = new BroadcastSender(inetAddress, this.port);
	}

	public static boolean isPacket(Intent pData, String pAction) {
		return pData.getAction().equals(pAction);
	}

	public void putIntentExtra(Intent pIntent, String pKey, String pValue) {
		pIntent.putExtra("__" + pKey + "__", pValue);
	}

	public String getIntentExtra(Intent pIntent, String pKey) {
		return pIntent.getStringExtra("__" + pKey + "__");
	}

	public String getMac() {
		return macAddress;
	}

	public String getIP() {
		return mWifiIP;
	}

	public void setMaxPackageSize(int pDefaultMaxPackageSize) {
		mDefaultMaxPackageSize = pDefaultMaxPackageSize;
	}

	public int getMaxPackageSize() {
		return mDefaultMaxPackageSize;
	}

	// acquire multicast lock on start
	public void start() {
		multicastLock = wifiMgr.createMulticastLock(packageName);

		sender.start();
		receiver.start();
	}

	protected Intent getSendIntent(Intent pIntent) {
		return pIntent;
	}

	protected Intent getReceiveIntent(Intent pIntent) {
		return pIntent;
	}

	protected String getMac(Intent pIntent) {
		return pIntent.getStringExtra(INTENT_EXTRA_MAC_TAG);
	}

	// release lock on stop
	public void stop() {
		multicastLock.release();
		sender.stop();
		receiver.stop();
	}

	/**
	 * Send messages to the port the {@link Broadcaster} is listening on.
	 * 
	 * @param pIntent
	 */
	public void send(Intent pIntent) {
		send(pIntent, port);
	}

	/**
	 * Send messages to any port you want to.
	 * 
	 * @param pIntent
	 * @param pPort
	 */
	public void send(Intent pIntent, int pPort) {
		if (pIntent == null)
			return;
		if (!pIntent.getAction().startsWith(INTENT_ACTION)) {
			Log.e(LOG_TAG, "Illegal action for intent. Intent cannot be send.");
			return;
		}
		pIntent.setAction(pIntent.getAction());
		if (getApplicationTag() != null)
			pIntent.putExtra(INTENT_EXTRA_APP_TAG, getApplicationTag());
		pIntent = getSendIntent(pIntent);
		sender.send(pIntent.toUri(0), pPort);
	}

	public String getApplicationTag() {
		return applicationTag;
	}

	public void setOnReceiveHandler(BroadcastReceiver.PacketListener pHandler) {
		receiver.setOnReceiveHandler(pHandler);
	}

	public BroadcastReceiver.PacketListener getOnReceiveHandler() {
		return receiver.getOnReceiveHandler();
	}

	public int getPort() {
		return port;
	}

	/**
	 * Calculate the broadcast IP we need to send the packet along. If we send
	 * it to 255.255.255.255, it never gets sent. I guess this has something to
	 * do with the mobile network not wanting to do broadcast.
	 * 
	 * @param context
	 */
	private InetAddress buildBroadcastAddress(Context context)
			throws IOException {
		WifiManager wifi = (WifiManager) context
				.getSystemService(Context.WIFI_SERVICE);

		DhcpInfo dhcp = wifi.getDhcpInfo();
		// handle null somehow

		int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
		byte[] quads = new byte[4];
		for (int k = 0; k < 4; k++)
			quads[k] = (byte) ((broadcast >> k * 8) & 0xFF);
		return InetAddress.getByAddress(quads);
	}

	private String getIpAddr(Context context) {
		WifiManager wifiManager = (WifiManager) context
				.getSystemService(Context.WIFI_SERVICE);
		WifiInfo wifiInfo = wifiManager.getConnectionInfo();
		int ip = wifiInfo.getIpAddress();

		String ipString = String.format("%d.%d.%d.%d", (ip & 0xff),
				(ip >> 8 & 0xff), (ip >> 16 & 0xff), (ip >> 24 & 0xff));

		return ipString;
	}
}
